import os
import re
import sys
import inspect
import importlib

import json
import yaml
import jinja2
from faker import Faker
from jsonpath import jsonpath

from src.yaml_util import Yaml
from src.config_util import Config
from conf.setting import BASE_PATH
from src.common import read_extract, clean_symbol, exec_sql


class SingletonFaker(object):
    instance = None
    init_flag = False

    def __init__(self, locale):
        if self.init_flag:
            return
        self.faker = Faker(locale)

    def __new__(cls, *args, **kwargs):
        if cls.instance is None:
            cls.instance = super().__new__(cls)
        return cls.instance


singleton_faker = SingletonFaker(locale='zh_CN').faker


class RenderTemplate(object):
    def __init__(self, obj, faker=singleton_faker):
        self.string = json.dumps(obj, ensure_ascii=False)
        self.faker = faker
        self.functions = self.__all_functions()
        self.env = Config().get_conf("env")

    def __all_functions(self):
        """加载common.py模块中的函数和faker中的函数"""
        sys.path.append(os.path.join(BASE_PATH, "src"))
        debug_module = importlib.import_module("common")
        all_function = dict(inspect.getmembers(debug_module, inspect.isfunction))
        for item in self.faker.__dir__():
            if not item.startswith("_"):
                try:
                    all_function[item] = getattr(self.faker, item)
                except Exception:
                    pass
        return dict(all_function)

    def __jinja_replace(self, string):
        """将${}转为{{}}; $.转为{{$.}}"""
        # ${}转为{{}}
        read_variable_re_1 = re.compile(r"\$\{.*?}")
        variables_1 = read_variable_re_1.findall(string)
        for v1 in variables_1:
            string = string.replace(v1, "{{" + v1[2: -1] + "}}")
        # $.xxx转为{{$.xxx}}
        read_variable_re_2 = re.compile(r'"\$\..*?"')
        variables_2 = read_variable_re_2.findall(string)
        for v2 in variables_2:
            string = string.replace(v2, "{{" + v2[1:-1] + "}}")
        return string

    def __digit_reg(self, string):
        """查找数字"""
        digit_re = re.compile(r'"[-]?[0-9\.]+",')
        return digit_re.findall(string, re.S)

    def render(self):
        replace_string = self.__jinja_replace(self.string)
        read_variable_re = re.compile(r"\{\{.*?}}")
        variables = read_variable_re.findall(replace_string)
        for v in variables:
            # 替换jsonpath表达式中变量到模版中
            if v[2:].startswith("$."):
                try:
                    # 优先从extract.yml文件中读取变量
                    result = self.extract_extract_info(v[2:-2])
                except Exception:
                    # extraction.yml中没有，则从conf.yml文件中的测试环境信息读取变量
                    result = self.extract_conf_info(v[2:-2])
                # 处理jsonpath中的字符，不然jinja2替换时会报错
                strip_v = clean_symbol(v)
                replace_string = replace_string.replace(v[2:-2], strip_v[2:-2])
                self.functions[strip_v[2:-2]] = result
            # 替换sql的执行结果到模版中
            elif "select" in v.lower() and "from" in v.lower():
                # 处理sql中的字符及空格，不然jinja2替换时会报错
                result = exec_sql(v[2:-2])
                strip_v = clean_symbol(v)
                replace_string = replace_string.replace(v[2:-2], strip_v[2:-2])
                # 将格式化后的sql与结果添加到functions中
                self.functions[strip_v[2:-2]] = result
            # 替换extract中的变量到模版中
            elif "(" not in v:
                # 将变量名与结果添加到functions中
                self.functions[v[2:-2]] = read_extract(v[2:-2])

        response = jinja2.Template(replace_string).render(self.functions)
        # 将字符串数字的双引号去掉，解决反序列化时字符串数字无法转为数字类型问题
        for item in self.__digit_reg(response):
            response = response.replace(item, item[1:-2] + ",")
        return yaml.safe_load(response)

    def extract_conf_info(self, expression):
        """通过jsonpath提取conf.yml中测试环境信息"""
        return jsonpath(Config("conf/env.yml").get_conf(Config().get_conf("env")), expression)[0]

    def extract_extract_info(self, expression):
        """通过jsonpath提取extract.yml中测试环境信息"""
        return jsonpath(Yaml(f"extraction/{self.env}_extract.yml").read_yml(), expression)[0]
